package edu.uci.ics.ai.maze.io;

import edu.uci.ics.ai.maze.models.Maze;
import edu.uci.ics.ai.maze.models.Polygon;
import edu.uci.ics.ai.maze.models.Vertex;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author dpurpura
 */
public class SimpleFileParser implements Parser {

  private static final Pattern polygonPattern
          = Pattern.compile("((\\(?\\s*\\d+,?\\s*\\d+\\s*\\)?\\s*)+c\\d)");
  private static final Pattern generalVerticesPattern
          = Pattern.compile("(\\(?\\s*\\d+,?\\s*\\d+\\s*\\)?\\s*)");
  private static final Pattern punctuatedVertexPattern
          = Pattern.compile("\\(\\s*(\\d+),\\s*(\\d+)\\s*\\)");
  private static final Pattern polygonPartsPattern
          = Pattern.compile("(.+)(c\\d)", Pattern.MULTILINE);
  private static final Pattern mazePattern
          = Pattern.compile("^(\\(?\\s*\\d+,?\\s*\\d+\\s*\\)?\\s*){2}(.*)$");
  
  @Override
  public Color parseColor(String s) {
    if (s == null) {
      throw new NullPointerException("Color is null");
    }

    s = s.trim();
    if (s.isEmpty() || s.length() != 2) {
      throw new IllegalArgumentException(
          "Color code is not two characters long [input = '" + s + "']");
    }

    int colorCode = Character.getNumericValue(s.charAt(1));
    switch (colorCode) {
      case 0:
        return Color.RED;
      case 1:
        return Color.BLUE;
      case 2:
        return Color.GREEN;
      case 3:
        return Color.ORANGE;
      case 4:
        return Color.MAGENTA;
      case 5:
        return Color.CYAN;
      case 6:
        return Color.GRAY;
      default:
        throw new IllegalArgumentException("Unknown color code [colorCode = '" + colorCode + "']");
    }
  }

  @Override
  public Vertex parseVertex(String s) {
    s = s.trim();
    try {
      if (s.startsWith("(")) {
        Matcher m = punctuatedVertexPattern.matcher(s);
        if (m.matches()) {
          return new Vertex(parseCoordinate(m.group(1)), parseCoordinate(m.group(2)));
        } else {
          throw new IllegalVertexException("Missing vertex coordinate(s) [input='" + s + "'].");
        }
      } else {
        String[] coords = s.split("\\s+");
        if (coords.length == 2) {
          return new Vertex(parseCoordinate(coords[0]), parseCoordinate(coords[1]));
        } else {
          throw new IllegalVertexException(
                  "Missing vertex (x, y) coordinate(s) [input='" + s + "'].");
        }
      }
    } catch(IllegalCoordinateException e) {
      throw new IllegalVertexException("Invalid Vertex [input='" + s + "'].", e);
    }
  }

  @Override
  public Integer parseCoordinate(String coord) throws IllegalArgumentException {
    try {
      Integer value = Integer.parseInt(coord.trim());
      if (value < 0) {
        throw new IllegalCoordinateException(
            "Coordinate cannot be negative [input='" + coord + "'].");
      }
      return value;
    } catch (NumberFormatException e) {
      throw new IllegalCoordinateException(
              "Unexpected coordinate value [input='" + coord + "'].", e);
    }
  }

  @Override
  public Polygon parsePolygon(String s) {
    s = s.trim();
    Matcher partsMatcher = polygonPartsPattern.matcher(s);
    Color color;
    if (partsMatcher.matches()) {
      String verticesStr = partsMatcher.group(1).trim();
      Matcher vertexMatcher = generalVerticesPattern.matcher(verticesStr);
      ArrayList<Vertex> vertices = new ArrayList<Vertex>();
      try {
        while (vertexMatcher.find()) {
          vertices.add(parseVertex(vertexMatcher.group()));
        }
      } catch (IllegalArgumentException e) {
        throw new IllegalPolygonException("Invalid polygon [input='" + s + "']", e);
      }
      if (vertices.isEmpty()) {
        throw new IllegalArgumentException(
                "Polygon does not have any vertices [input='" + verticesStr + "'].");
      }

      if (partsMatcher.find(2)) {
        color = parseColor(partsMatcher.group(2));
      } else {
        throw new IllegalArgumentException("Polygon is missing its color [input='" + s + "'].");
      }
      return new Polygon(vertices, color);
    } else {
      throw new IllegalArgumentException("Not a valid Polygon [input='" + s + "']");
    }
  }

  @Override
  public Maze parseMaze(String s) {
    return parseMaze(s, null);
  }

  public Maze parseMaze(String input, String name) {
    Matcher vertexMatcher = generalVerticesPattern.matcher(input);

    Vertex start;
    if (vertexMatcher.find()) {
      start = parseVertex(vertexMatcher.group());
    } else {
      throw new IllegalArgumentException(
              "Invalid Maze, missing start vertex.");
    }

    Vertex goal;
    if (vertexMatcher.find()) {
      goal = parseVertex(vertexMatcher.group());
    } else {
      throw new IllegalArgumentException(
              "Invalid Maze, missing goal vertex.");
    }

    Matcher mazeMatcher = mazePattern.matcher(input);
    mazeMatcher.matches();
    return new Maze(start, goal, parsePolygons(mazeMatcher.group(2)), name);
  }

  List<Polygon> parsePolygons(String s) {
    Matcher polygonMatcher = polygonPattern.matcher(s);
    List<Polygon> polygons = new ArrayList<Polygon>();
    while (polygonMatcher.find()) {
      polygons.add(parsePolygon(polygonMatcher.group().trim()));
    }
    return polygons;
  }

  /**
   * Returns the Maze stored in the file.
   *
   * @param filename the name of the file containing the Maze
   * @return the Maze
   * @throws FileNotFoundException
   * @throws IOException
   */
  public static Maze parseFile(String filename) throws FileNotFoundException, IOException {
    BufferedReader reader = null;
    try {
      reader = new BufferedReader(new FileReader(filename));
      StringBuilder builder = new StringBuilder();
      while (reader.ready()) {
        builder.append(reader.readLine());
        builder.append(" ");
      }
      return new SimpleFileParser().parseMaze(builder.toString(), filename);
    } finally {
      if (reader != null) {
        reader.close();
      }
    }
  }

}
